This Technical Note explains how to write an SNMP (Simple Network Management Protocol) Transport. An SNMP Transport is responsible for communicating between the SNMP Manager and a particular network layer. Thus, if you were writing a new network stack for the Macintosh and wanted it to use the SNMP Manager, you would write an SNMP Transport for your network stack.
Topics
It is assumed in this document that you understand MacSNMP, object-oriented programming and the Shared Library Manager. The SNMP Manager is built using the Shared Library Manager and is a set of shared libraries. The SNMP Manager shared library contains the implementation of the base class for an SNMP Transport. Your transport must be a subclass of this SNMP Transport class. For more information on MacSNMP and the Shared Library Manager see the E.T.O. CD ROM.
This Tech Note provides some additional information needed to construct an SNMP Transport for a particular network stack. An SNMP Transport is required to know about all of the specific idiosyncrasies of SNMP for a particular network stack. SNMP was originally defined to run over the TCP stack using UDP. The IETF (Internet Engineering Task Force) has also defined how to run SNMP over three other network stacks: AppleTalk, IPX, and OSI. These methods are documented by the IETF proposed standards: RFC 1419--SNMP over AppleTalk, RFC 1420--SNMP over IPX, and RFC 1418--SNMP over OSI. There is also an informational RFC draft that describes what must be defined to allow SNMP to run over any particular network stack.
Apple provides SNMP transports that support the TCP stack and the AppleTalk stack. The TCP SNMP Transport provides knowledge of IP addresses, listens on the well-known SNMP sockets, sends SNMP packets on UDP, and resolves addresses in dotted notation into IP addresses for sending Traps. (An SNMP Trap is the method that SNMP entities use to send unsolicited warning messages to each other and should not be confused with a Macintosh Operating System Trap.) The AppleTalk SNMP Transport likewise provides knowledge of AppleTalk addresses, listens on the well-known SNMP sockets, sends SNMP packets on DDP, and resolves NBP (Name Binding Protocol) names into AppleTalk addresses for sending Traps. Similarly, a new SNMP Transport would have to provide these same services: understanding the new network addresses, listening for SNMP packets, sending SNMP packets, and resolving stored console addresses to network addresses for sending Traps.
Figure 1--An SNMP Transport provides the interface between the SNMP Manager and a particular network stack. Creating an SNMP Transport
The file Transport.h defines the SNMP Transport class. To create an SNMP Transport, you must subclass the SNMP Transport class. The Shared Library Manager allows a run-time link of your SNMP Transport subclass with the base object SNMP Transport provided in the SNMP Manager shared library. The definition of the SNMP Transport object is as follows:
/********************************************************************** ** Class TSNMPTransport ***********************************************************************/ #define kTSNMPTransportID "snmp:mgr$TSNMPTrans" #define kLIB_TransID "snmp:trans$" // Library ID for an SNMP Transport class TSNMPTransport : public TDynamic { public: TSNMPTransport(); virtual ~TSNMPTransport(); virtual Boolean IsValid() const; // returns valid or not virtual OSErr InitSNMPTransport( TransportTag aTag, TIAddressPtr aTrapSocketPtr, TIAddressPtr aReqSocketPtr, Boolean ahandlesresolution, short aWritebufsize, TransportRWProcPtr aWriteProcPtr, short aNumofReads, short aReadTIAddrSRCmax, short aReadTIAddrDESTmax, short aReadbufsize, TransportRWProcPtr aReadProcPtr); virtual void SNMPWriteDone(SNMPTransportBlockPtr snmpPtr); virtual void SNMPReadDone(SNMPTransportBlockPtr snmpPtr); TSNMPManagerPrv* fSNMPManagerPtr; friend TSNMPManagerPrv; protected: Boolean fValid; Boolean fhandlesresolution; short fNumofReads; short fReadTIAddrSRCmax; short fReadTIAddrDESTmax; short fReadbufsize; short fWritebufsize; private: TransportTag fTag; TIAddressPtr fTrapSocketPtr; TIAddressPtr fReqSocketPtr; TransportRWProcPtr fWriteProcPtr; TransportRWProcPtr fReadProcPtr; };
To start an SNMP Transport you must instantiate it. For the AppleTalk SNMP Transport, we install a process on the AppleTalk transition queue that tells us when AppleTalk is coming up or going down and instantiate or destroy the Transport as appropriate. For the TCP SNMP Transport, an INIT31 instantiates the Transport and it is never destroyed. For your Transport you must provide the code that instantiates and destroys your Transport.
When a Transport is instantiated the base class constructor is called first. The base class constructor fills in the fSNMPManagerPtr field with a pointer to a class TSNMPManagerPrv, which can be cast to a pointer to the class TSNMPManager defined in TSNMP.h. This pointer can be used to access the members of the TSNMPManager class. The constructor also adds a pointer to your SNMP Transport object to a queue of transports so that it can find you later, and if everything worked, sets the fValid field to true. Your constructor is then called. If the fValid field is not set to true on the entrance to your constructor, you should bail out of the constructor immediately. If your constructor fails for some reason, you should set the fValid field to false. The Shared Library Manager will then clean up the object so that a partially constructed one does not remain.
Initializing an SNMP Transport
After your Transport is constructed it must be initialized. Your transport will not work until InitSNMPTransport() is called. This routine sets all of the fields in the SNMP Transport from the parameter values that you pass in. You must call the inherited InitSNMPTransport member function if you override it. The following fields must be set.
-> TransportTag aTag A long that uniquely identifies a transport (analogous to OSType) -> TIAddressPtr aTrapSocketPtr Opaque address of where the Transport listens for Traps -> TIAddressPtr aReqSocketPtr Opaque address of where the Transport listens for Requests -> Boolean ahandlesresolution True if the transport can send Traps -> short aWritebufsize Maximum size of write buffers -> TransportRWProcPtr aWriteProcPtr Address of the write procedure -> short aNumofReads Maximum number of reads issued at once -> short aReadTIAddrSRCmax Maximum length of source address for read operations -> short aReadTIAddrDESTmax Maximum length of destination address for read operations -> short aReadbufsize Maximum size of a read buffer -> TransportRWProcPtr aReadProcPtr Address of the read procedure
The aReadProcPtr and aWriteProcPtr have the following definition:
typedef void (*TransportRWProcPtr)(SNMPTransportBlockPtr dataPtr);
It is recommended that the TransportTag be four human readable ASCII characters that describe the network layer that the Transport talks to. If multiple Transports with the same TransportTag are instantiated, the SNMP Manager will ignore all but the first one instantiated. For the AppleTalk SNMP Transport the tag is 'DDP ', for the TCP SNMP Transport the tag is 'UDP '. This value is used in the Trap Table in the Macintosh Agent to determine which Transport understands the console address stored in a particular row of the table. The sockets are where the Transport listens for Traps and Requests. The SNMP Manager does not understand the format of these addresses and just passes them along to your Transport. They are also used as the source of Traps or Responses sent. Finally, they allow the SNMP Manager to determine what type of packet it is decoding before parsing the raw ASN.1 data. In our Transports we have stored pointers to the actual bits of the network layer addresses in these fields. The aWritebufsize and aReadbufsize are the size of buffers that the SNMP Manager allocates for your Transport to use.
Reading SNMP Packets
Finally, InitSNMPTransport() tries to issue aNumofReads outstanding reads by calling your aReadProc() with a filled in SNMPTransportBlock as shown in Figure 2. The SNMPTransportBlock is defined as follows:
struct SNMPTransportBlock { unsigned long qLink; // reserved for pointer to next block short qType; // reserved for queue routines TSNMPTransport* transport; // who was asked to read or write block SNMPError result; // after request is serviced void* destopaqueData;// destination address to be resolved (used in write trap only) TIAddressPtr destination; // who the packet was sent to TIAddressPtr source; // who sent the packet to us void * UserDataPtr; // Transport work space Boolean freeFlag; // is the write finished? Boolean readFlag; // managed by SNMP Manager PacketElementPtr pktelementPtr;// managed by SNMP Manager SNMPPacketStructPtr packetPtr; // managed by SNMP Manager };
The aReadProc() must be able to catch both incoming Traps and Requests. It must also be able to be called at any time, thus you must not allocate memory using the normal Macintosh memory calls. You may use the area pointed to by UserDataPtr for any scratch you might need. The SNMP Manager has preallocated this area for you according to the sizes you set in aReadbufsize. It must also return immediately. When a read actually completes you must call SNMPReadDone() after filling in the data, the source and destination addresses, the actual number of bytes read (in packetPtr->packetPiece.dataSize), the freeFlag (set to false), and the result (snmpNoError if it worked.)
Warning: If the freeFlag is incorrectly set, the SNMP Manager will become hopelessly confused.
After the read packet is processed by the SNMP Manager it issues another read call to aReadProc() so that there will always be some number of outstanding reads. If there are no outstanding reads at any time it is allowable for your transport to drop SNMP packets.
Figure 2--Layout of SNMPTransportBlock when aReadProc() called. Only the fields that the Transport may need to access are shown.
When your Transport is deleted you must ensure that your completion routines will not be called after your destructor is finished and your Transport object is gone. You may have to wait for asynchronous writes to complete and cancel any outstanding reads. The base class destructor will ensure that any packets you have queued up for processing will be thrown away.
Figure 3--Layout of SNMPTransportBlock when the SNMP Manager calls aWriteProc() with a response to an SNMP Request.* *Only the fields that the Transport may need to access are shown.
Writing SNMP Packets
After a packet is processed by the SNMP Manager, it will almost always generate a Response packet. The SNMP Manager will either respond to a Request or generate a Trap. In the cases of a simple Response the SNMP Manager will call your aWriteProc() with an SNMPTransportBlock filled out as shown in Figure 3. Your aWriteProc() may be called at any time, thus you must not allocate memory using the normal Macintosh memory calls. You may use the area pointed to by UserDatPtr for any scratch memory you need. The SNMP Manager has preallocated this area for you according to the sizes you set in aWritebufsize. The destination socket is the same block as was passed in on the read as the source socket, the source socket is your initialized fRequestSocketPtr, the packetPtr points to the SNMP data to put on the wire. You must return immediately from the call to aWriteProc(). After the write completes you should fill in the result, set the freeFlag to true, and call SNMPWriteDone() so that the SNMP Manager can free up the memory it allocated.
Warning: If the freeFlag is incorrectly set the SNMP Manager will become hopelessly confused.
If your Transport can send Traps it must set the fHandlesResolution field to true. It is strongly encouraged that your transport handle Traps, if it did not, it would not be fully compliant with the SNMP standard. When a Trap is generated, the SNMP Manager will determine if the Trap is supposed to be sent to a console that supports your Transport type by looking through the Trap table in the Macintosh System MIB implemented by the Macintosh Agent. If so, it will issue a call to your aWriteProc() as above, except that the destOpaqueData will point to a block that the SNMP Manager has procured from the trapDestination field in the Trap Table in the Macintosh Agent. It is up to you to define how this address will be stored. However, it must be nonvolatile between reboots of the system. For the AppleTalk SNMP transport, we store the NBP name of the console as specified in the AppleTalk over SNMP RFC. For the TCP SNMP Transport, we store the IP address of the console in dotted notation The Transport is responsible for turning this address into the wire address of the console. Everything else is as above. You must return immediately after the call to your aWriteProc() and call SNMPWriteDone() when the Trap write completes.
The Trap Table is implemented by the Macintosh Agent and is defined in ASN.1 by the Macintosh System MIB as:
TrapRequestEntry ::= SEQUENCE { trapIndex INTEGER, // unsigned long trapCommunity MacintoshDisplayString, // opaque data trapProtocol MacOSType, // four bytes trapDestination OCTET STRING, // opaque data trapValidity INTEGER // 1 = valid, 2 = invalid }
The two fields that an SNMP Transport author must define are the trapProtocol and the trapDestination. The trapProtocol is compared by the SNMP Manager to the TransportTag and is used to identify which SNMP Transport will be able to resolve the trapDestination entry and send the Trap. The trapProtocol field must contain the same value that was initialized in the transportTag field. Some care should be given to the format of the trapDestination as network managers will have to enter these addresses by hand from a console.
An SNMP Transport provides an interface between the SNMP Manager and a network layer of a particular network stack. It must be able to be called at any time, thus cannot depend on the Macintosh memory calls. A Transport must understand how a Trap destination (console address) is to be stored in the Macintosh Agent's Trap Table. This address format must be stable between reboots of the system and must be resolvable into a network address for the console. It is the responsibility of the Transport developer to inform network managers of how to store this address. For IPX, AppleTalk, and OSI, these standards have been specified in RFCs (Request For Comments). These documents are available on-line off of the Internet and are maintained in various repositories and formats by the IETF.
Further Reference: